﻿using System;
using System.Collections.Generic;
using System.Text;
using Yorrick.FlickrMetadataSynchr.Properties;
using System.Globalization;

namespace Yorrick.FlickrMetadataSynchr.Local
{
    /// <summary>
    /// Class that helps with GPS coordinateValues
    /// </summary>
    internal static class GpsCoordinatesHelper
    {
        #region Private constants
        /// <summary>
        /// The factor to use when rounding off coordinates.
        /// </summary>
        private const double _CoordinateRoundingFactor = 1000000.0;

        /// <summary>
        /// The factor to use when rounding off the number of seconds.
        /// </summary>
        private const double _SecondsRoundingFactor = 1000.0;

        /// <summary>
        /// The denominator to use for encoding coordinate parts.
        /// </summary>
        private const uint _DegreesDenominator = 1000000;
        #endregion

        #region Public methods
        /// <summary>
        /// Decodes a set of three EXIF GPS coordinate values and converts it to a single double precision floating point coordinate.
        /// </summary>
        /// <param name="coordinateValues">The encoded GPS values.</param>
        /// <returns>The coordinate as a double precision floating point value.></returns>
        public static double DecodeExifCoordinate(ulong[] coordinateValues)
        {
            int degrees = (int)ConvertExifCoordinatePart(coordinateValues[0]);
            int minutes = (int)ConvertExifCoordinatePart(coordinateValues[1]);
            double seconds = ConvertExifCoordinatePart(coordinateValues[2]);

            double coordinate = ConstructCoordinate(degrees, minutes, seconds);

            return coordinate;
        }

        /// <summary>
        /// Encodes a decimal coordinate to the GPS EXIF encoded coordinate.
        /// </summary>
        /// <param name="coordinate">The decimal coordinate.</param>
        /// <returns>The converted GPS EXIF encoded coordinate.</returns>
        public static ulong[] EncodeExifCoordinate(double coordinate)
        {
            ulong[] coordinates = new ulong[3];

            // Make sure coordinate is positive.
            coordinate = Math.Abs(coordinate);

            double degrees = Math.Floor(coordinate);

            coordinate -= degrees;

            double minutes = Math.Floor(coordinate * 60.0);

            coordinate -= (minutes / 60.0);

            double seconds = Math.Round(coordinate * 3600.0 * _SecondsRoundingFactor) / _SecondsRoundingFactor;

            coordinates[0] = EncodeExifCoordinatePart(degrees, 1);
            coordinates[1] = EncodeExifCoordinatePart(minutes, 1);
            coordinates[2] = EncodeExifCoordinatePart(seconds, _DegreesDenominator);

            return coordinates;
        }


        /// <summary>
        /// Encodes a decimal coordinate to the GPS XMP encoded coordinate.
        /// </summary>
        /// <param name="coordinate">The decimal coordinate.</param>
        /// <returns>The converted GPS XMP encoded coordinate.</returns>
        public static string EncodeXmpCoordinate(double coordinate)
        {
            return coordinate.ToString("###.#######;-###.######;0.0", CultureInfo.InvariantCulture);
        }

        #endregion

        #region Private methods
        /// <summary>
        /// Constructs a coordinate as a double precision floating point value from degrees, minutes and seconds.
        /// </summary>
        /// <param name="degrees">The value of degrees.</param>
        /// <param name="minutes">The value of minutes.</param>
        /// <param name="seconds">The value of seconds.</param>
        /// <returns>The coordinate as a double precision floating point value.</returns>
        /// <remarks>The value is rounded to prevent roundtripping problems.</remarks>
        private static double ConstructCoordinate(int degrees, int minutes, double seconds)
        {
            double coordinate = (double)degrees + (minutes / 60.0) + (seconds / 3600.0);

            double roundedCoordinate = Math.Floor(coordinate * _CoordinateRoundingFactor) / _CoordinateRoundingFactor;

            return roundedCoordinate;
        }

        /// <summary>
        /// Converts an EXIF GPS coordinate part.
        /// </summary>
        /// <param name="value">The coordinate value as a 64-bit unsigned integer.</param>
        /// <returns>The coordinate as a floating point value.</returns>
        private static double ConvertExifCoordinatePart(ulong value)
        {
            byte[] bytes = BitConverter.GetBytes(value);

            uint numerator = BitConverter.ToUInt32(bytes, 0);
            uint denominator = BitConverter.ToUInt32(bytes, 4);

            return ((double)numerator / (double)denominator);
        }

        /// <summary>
        /// Encodes an EXIF GPS coordinate part as a 64-bit unsigned long integer.
        /// </summary>
        /// <param name="coordinatePart">The coordinate part value to encode.</param>
        /// <param name="denominator">The denominator to use for the encoding.</param>
        /// <returns>The coordinate part encded as a 64-bit unsigned long integer.</returns>
        private static ulong EncodeExifCoordinatePart(double coordinatePart, uint denominator)
        {
            byte[] coordinatePartBytes = new byte[8];

            uint numerator = (uint) (coordinatePart * denominator);

            byte[] numeratorBytes = BitConverter.GetBytes(numerator);
            byte[] denominatorBytes = BitConverter.GetBytes(denominator);

            numeratorBytes.CopyTo(coordinatePartBytes, 0);
            denominatorBytes.CopyTo(coordinatePartBytes, 4);

            ulong encodedCoordinatePart = BitConverter.ToUInt64(coordinatePartBytes, 0);

            return encodedCoordinatePart;
        }
        #endregion
    }
}
